#pragma once
#include"oj_model.hpp"
#include"oj_view.hpp"
#include"../comm/Log.hpp"
#include"../comm/Util.hpp"
#include"../comm/httplib.h"
#include<vector>
#include<mutex>
#include<cassert>
#include<cstring>
#include<fstream>
#include<jsoncpp/json/json.h>

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_log;
    using namespace ns_util;
    using namespace httplib;

    // 提供服务的主机
    class Machine
    {
    public:
        // 提升主机负载
        void IncLoad()
        {
            if(_mtx) _mtx->lock();
            ++_load;
            if(_mtx) _mtx->unlock();
        }
        // 减少主机负载
        void DecLoad()
        {
            if(_mtx) _mtx->lock();
            --_load;
            if(_mtx) _mtx->unlock();
        }
        // 重置负载
        void ResetLoad()
        {
            if(_mtx) _mtx->lock();
            _load = 0;
            if(_mtx) _mtx->unlock();
        }
        // 获取主机负载
        uint64_t Load()
        {
            uint64_t load = 0;
            if(_mtx) _mtx->lock();
            load = _load;
            if(_mtx) _mtx->unlock();
            return load;
        }
    public:
        std::string _ip = "";       //编译服务的ip
        int _port = 0;              //编译服务的port
        uint64_t _load = 0;         //编译服务的负载
        std::mutex *_mtx = nullptr; // mutex禁止拷贝的，使用指针
    };

    // 负载均衡模块
    const std::string _machine_conf = "./conf/service_machine.conf";
    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadConf(_machine_conf));
            logMessage(NORMAL, "加载\"%s\"成功...", _machine_conf.c_str());
        }
        bool LoadConf(const std::string& machine_conf)
        {
            std::ifstream in(machine_conf);
            if(!in.is_open())
            {
                logMessage(FATAL, "加载\"%s\"失败...[%s][%d]: %s", machine_conf.c_str(), __FILE__, __LINE__, strerror(errno));
                return false;
            }
            std::string line;
            while(std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if(tokens.size() != 2)
                {
                    logMessage(WARNING, "切分%s失败...[%s][%d]: %s", line, __FILE__, __LINE__, strerror(errno));
                    continue;
                }
                Machine m;
                m._ip = tokens[0];
                m._port = stoi(tokens[1]);
                m._load = 0;
                m._mtx = new std::mutex;

                _online.push_back(_machines.size());
                _machines.push_back(m);
            }
            in.close();
            return true;
        }
        // id: 输出型参数
        // m : 输出型参数
        bool SmartChoice(int* id, Machine** m)
        {
            // 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
            _mtx.lock();
            // 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash
            int online_num = _online.size();
            if(online_num == 0)
            {
                _mtx.unlock();
                logMessage(FATAL, "所有的后端编译主机已经离线, 请运维的同事尽快查看...[%s][%d]: %s", __FILE__, __LINE__, strerror(errno));
                return false;
            }
            // 通过遍历的方式，找到所有负载最小的机器
            *id = _online[0];
            *m = &_machines[_online[0]];
            uint64_t min_load = _machines[_online[0]].Load();
            for(int i = 1; i < online_num; ++i)
            {
                uint64_t curr_load = _machines[_online[i]].Load();
                if (min_load > curr_load)
                {
                    min_load = curr_load;
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }
            _mtx.unlock();
            return true;
        }
        void OnlineMachine()
        {
            // 我们统一上线，后面统一解决
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
            _mtx.unlock();

            logMessage(NORMAL, "所有的主机又上线啦！");
        }
        void OfflineMachine(int which)
        {
            _mtx.lock();
            for(auto iter = _online.begin(); iter != _online.end(); ++iter)
            {
                if(*iter == which) // 要离线的主机已经找到啦
                {
                    _machines[which].ResetLoad();
                    _online.erase(iter);
                    _offline.push_back(which);
                    break; // 因为break的存在，所有我们暂时不考虑迭代器失效的问题
                }
            }
            _mtx.unlock();
        }
        //for test
        void ShowMachines()
        {
             _mtx.lock();
             std::cout << "当前在线主机列表: ";
             for(auto &id : _online)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             std::cout << "当前离线主机列表: ";
             for(auto &id : _offline)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             _mtx.unlock();
        }
    private:
        std::vector<Machine> _machines; // 可以给我们提供编译服务的所有的主机，每一台主机都有自己的下标，充当当前主机的id
        std::vector<int> _online;       // 所有在线的主机id
        std::vector<int> _offline;      // 所有离线的主机id
        std::mutex _mtx;                // 保证LoadBlance它的数据安全
    };

    class Control
    {
    public:
        void RecoverMachines()
        {
            _load_balance.OnlineMachine();
        }
        bool AllQuestions(std::string *html)
        {
            std::vector<Question> all;
            if(_model.GetAllQuestions(&all))
            {
                sort(all.begin(), all.end(), [](const Question& q1, const Question& q2){
                    return stoi(q1._number) < stoi(q2._number);
                });
                // 获取题目信息成功，将所有的题目数据构建成网页
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }
            return true;
        }
        bool OneQuestion(const std::string &number, std::string *html)
        {   
            Question q;
            if(_model.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功，将所有的题目数据构建成网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html =  "指定题目: " + number + " 不存在!";
                return false;
            }
            return true;
        }
        void Judge(const std::string& number, const std::string& in_json, std::string* out_json)
        {
            // 0. 根据题目编号，直接拿到对应的题目细节
            Question q;
            _model.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化，得到题目的id，得到用户提交源代码，input
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);

            // 2. 重新拼接用户代码+测试用例代码，形成新的代码
            Json::Value compile_value;
            compile_value["code"] = in_value["code"].asString() + "\n" + q._tail;
            compile_value["input"] = in_value["input"].asString();
            compile_value["cpu_limit"] = q._cpu_limit;
            compile_value["mem_limit"] = q._mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value);

            // 3. 选择负载最低的主机(差错处理)
            // 规则: 一直选择，直到主机可用，否则，就是全部挂掉
            while(true)
            {
                int id = 0;
                Machine* m = nullptr;
                if(!_load_balance.SmartChoice(&id, &m)) break;

                // 4. 然后发起http请求，得到结果
                Client cli(m->_ip, m->_port);
                m->IncLoad();
                logMessage(NORMAL, "选择主机成功, 主机id: %d, 详情: %s:%d, 当前主机的负载是: %d...", id, m->_ip.c_str(), m->_port, m->Load());
                if(auto res = cli.Post("/compile_and_run", compile_string, "application/json; charset=utf-8"))
                {
                    // 5. 将结果赋值给out_json
                    if(res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        logMessage(NORMAL, "请求编译和运行服务成功...");
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    //请求失败
                    logMessage(ERROR, "当前请求的主机id: %d, 详情: %s:%d 可能已经离线...", id, m->_ip, m->_port);
                    _load_balance.OfflineMachine(id);
                    _load_balance.ShowMachines(); //仅仅是为了用来调试
                }
            }

        }
    private:
        Model _model;
        View _view;
        LoadBalance _load_balance;
    };
}